﻿// Copyright (c) 2012, Alexander Endris
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#region Usings

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using JungleTimer.LoL;
using JungleTimer.Native;
using JungleTimer.Properties;
using Settings = JungleTimer.LoL.Settings;
using Timer = System.Timers.Timer;

#endregion

namespace JungleTimer.Windows
{
  public partial class JungleTimer : Form
  {
    #region Public / Constructors

    public JungleTimer()
    {
      settings = Settings.LoadSettings(settingsPath);
      gameTime = new Stopwatch();
      timer = new Timer();
      timer.Interval = 1000;
      timer.Elapsed += TimerOnTick;
      if (Directory.Exists(Path.Combine(settings.LoLPath, logsPath)))
      {
        fileSystemWatcher = new FileSystemWatcher(Path.Combine(settings.LoLPath, logsPath));
        fileSystemWatcher.EnableRaisingEvents = true;
        fileSystemWatcher.Created += FileSystemWatcherOnCreated;
        fileSystemWatcher.Changed += FileSystemWatcherOnChanged;

        autotimer = new Timer();
        autotimer.Elapsed += AutotimerOnTick;
        autotimer.Interval = 500;
        autotimer.Enabled = false;

        autostart = true;
      }

      InitializeComponent();

      Region = Helper.BitmapToRegion(Resources.Teemo_Jungle, Color.White);

      InitializeLabels();

      Keyboard k = new Keyboard();
      k.RawInputEvent += KOnRawInputEvent;
      k.Initialize(this.Handle);
      Application.AddMessageFilter(k);
    }

    #endregion

    #region Public / Methods

    public void ProcessKeyDown(Keys key)
    {
      if (!keysDown.Contains(key))
        keysDown.Add(key);
    }

    public void ProcessKeyUp(Keys key)
    {
      if (keysDown.Contains(key))
      {
        ProcessHotkey(key);
        keysDown.Remove(key);
      }
    }

    #endregion

    #region Protected / Methods

    protected override void WndProc(ref Message m)
    {
      base.WndProc(ref m);
      switch (m.Msg)
      {
        case WM_NCHITTEST:
          if (m.Result == (IntPtr)HTCLIENT)
            m.Result = (IntPtr)HTCAPTION;
          break;
      }
    }

    #endregion

    #region Private / Constants

    private const int HTCAPTION = 2;
    private const int HTCLIENT = 1;
    private const int WM_NCHITTEST = 0x0084;

    #endregion

    #region Private / Attributes

    private static readonly string logsPath = @"Logs\Game - R3d Logs\";
    private static readonly string settingsPath = Path.Combine(Environment.CurrentDirectory, "cfg.xml");
    private bool autostart;
    private Timer autotimer;

    private Respawn baron;
    private string currentGameFile;
    private Respawn dragon;
    private FileSystemWatcher fileSystemWatcher;
    private Stopwatch gameTime;
    private List<Keys> keysDown = new List<Keys>();
    private object locker = new object();
    private Respawn ourBlue;
    private Respawn ourRed;
    private Settings settings;
    private bool started;
    private Respawn theirBlue;
    private Respawn theirRed;
    private Timer timer;

    #endregion

    #region Private / Methods

    private void AnnounceRespawn(string name, Respawn respawn, TimeSpan elapsed)
    {
      if (settings.Announce1Minute && (respawn.Time - elapsed).TotalSeconds <= 60 &&
          respawn.State == AnnouncementState.One)
      {
        AnnounceRespawnInMinute(name);
        respawn.State = AnnouncementState.Thirty;
      }
      else if (settings.Announce30Seconds && (respawn.Time - elapsed).TotalSeconds <= 30 &&
               respawn.State == AnnouncementState.Thirty)
      {
        AnnounceRespawnIn30(name);
        respawn.State = AnnouncementState.Ten;
      }
      else if (settings.Announce10Seconds && (respawn.Time - elapsed).TotalSeconds <= 10 &&
               respawn.State == AnnouncementState.Ten)
      {
        AnnounceRespawnIn10(name);
        respawn.State = AnnouncementState.None;
      }
    }

    private void AnnounceRespawnIn10(string name)
    {
      Speech.Announce(string.Format("{0} in 10 seconds.", name));
    }

    private void AnnounceRespawnIn30(string name)
    {
      Speech.Announce(string.Format("{0} in 30 seconds.", name));
    }

    private void AnnounceRespawnInMinute(string name)
    {
      Speech.Announce(string.Format("{0} in 1 minute.", name));
    }

    private void AnnounceRespawns(TimeSpan elapsed)
    {
      if (settings.SpeechEnabled)
      {
        AnnounceRespawn("Baron", baron, elapsed);
        AnnounceRespawn("Dragon", dragon, elapsed);
        AnnounceRespawn("Our blue", ourBlue, elapsed);
        AnnounceRespawn("Our red", ourRed, elapsed);
        AnnounceRespawn("Their blue", theirBlue, elapsed);
        AnnounceRespawn("Their red", theirRed, elapsed);
      }
    }

    private void AutotimerOnTick(object sender, EventArgs eventArgs)
    {
      if (currentGameFile == null)
        return;

      string file;

      FileStream fs = new FileStream(currentGameFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
      StreamReader sr = new StreamReader(fs);
      file = sr.ReadToEnd();

      if (!started && file.Contains("StartGame"))
      {
        gameTime.Start();
        autotimer.Enabled = false;
        autotimer.Interval = 2000;
        autotimer.Enabled = true;
      }
      else if (started && file.Contains("Finished Main Loop."))
      {
        started = false;
        gameTime.Stop();
        autotimer.Enabled = false;
        autotimer.Interval = 500;
      }
    }

    private void BtnBaronClick(object sender, EventArgs e)
    {
      Keybinding binder = new Keybinding();

      if (binder.ShowDialog() == DialogResult.OK)
      {
        if (!(settings.HotkeyDragon == binder.Key || settings.HotkeyOurBlue == binder.Key ||
              settings.HotkeyOurRed == binder.Key || settings.HotkeyTheirBlue == binder.Key ||
              settings.HotkeyTheirRed == binder.Key))
        {
          settings.HotkeyBaron = binder.Key;
          Settings.SaveSettings(settingsPath, settings);
          if (!started)
            InitializeLabels();
          else
            UpdateLabels(GetElapsed());
        }
        else
        {
          MessageBox.Show(this,"This is already bound!\r\nNothing has changed.", "Keybinder", MessageBoxButtons.OK);
        }
      }
    }

    private void BtnDragonClick(object sender, EventArgs e)
    {
      Keybinding binder = new Keybinding();

      if (binder.ShowDialog() == DialogResult.OK)
      {
        if (!(settings.HotkeyBaron == binder.Key || settings.HotkeyOurBlue == binder.Key ||
            settings.HotkeyOurRed == binder.Key || settings.HotkeyTheirBlue == binder.Key ||
            settings.HotkeyTheirRed == binder.Key || settings.ManualStartStopHotkey == binder.Key))
        {
          settings.HotkeyDragon = binder.Key;
          Settings.SaveSettings(settingsPath, settings);
          if (!started)
            InitializeLabels();
          else
            UpdateLabels(GetElapsed());
        }
        else
        {
          MessageBox.Show(this,"This is already bound!\r\nNothing has changed.", "Keybinder", MessageBoxButtons.OK);
        }
      }
    }

    private void BtnOurBlueClick(object sender, EventArgs e)
    {
      Keybinding binder = new Keybinding();

      if (binder.ShowDialog() == DialogResult.OK)
      {
        if (!(settings.HotkeyDragon == binder.Key || settings.HotkeyBaron == binder.Key ||
          settings.HotkeyOurRed == binder.Key || settings.HotkeyTheirBlue == binder.Key ||
          settings.HotkeyTheirRed == binder.Key || settings.ManualStartStopHotkey == binder.Key))
        {
          settings.HotkeyOurBlue = binder.Key;
          Settings.SaveSettings(settingsPath, settings);
          if (!started)
            InitializeLabels();
          else
            UpdateLabels(GetElapsed());
        }
        else
        {
          MessageBox.Show(this,"This is already bound!\r\nNothing has changed.", "Keybinder", MessageBoxButtons.OK);
        }
      }
    }

    private void BtnOurRedClick(object sender, EventArgs e)
    {
      Keybinding binder = new Keybinding();

      if (binder.ShowDialog() == DialogResult.OK)
      {
        if (!(settings.HotkeyDragon == binder.Key || settings.HotkeyOurBlue == binder.Key ||
          settings.HotkeyBaron == binder.Key || settings.HotkeyTheirBlue == binder.Key ||
          settings.HotkeyTheirRed == binder.Key || settings.ManualStartStopHotkey == binder.Key))
        {
          settings.HotkeyOurRed = binder.Key;
          Settings.SaveSettings(settingsPath, settings);
          if (!started)
            InitializeLabels();
          else
            UpdateLabels(GetElapsed());
        }
        else
        {
          MessageBox.Show(this,"This is already bound!\r\nNothing has changed.", "Keybinder", MessageBoxButtons.OK);
        }
      }
    }

    private void BtnTheirBlueClick(object sender, EventArgs e)
    {
      Keybinding binder = new Keybinding();

      if (binder.ShowDialog() == DialogResult.OK)
      {
        if (!(settings.HotkeyDragon == binder.Key || settings.HotkeyOurBlue == binder.Key ||
         settings.HotkeyOurRed == binder.Key || settings.HotkeyBaron == binder.Key ||
         settings.HotkeyTheirRed == binder.Key || settings.ManualStartStopHotkey == binder.Key))
        {
          settings.HotkeyTheirBlue = binder.Key;
          Settings.SaveSettings(settingsPath, settings);
          if (!started)
            InitializeLabels();
          else
            UpdateLabels(GetElapsed());
        }
        else
        {
          MessageBox.Show(this,"This is already bound!\r\nNothing has changed.", "Keybinder", MessageBoxButtons.OK);
        }
      }
    }

    private void BtnTheirRedClick(object sender, EventArgs e)
    {
      Keybinding binder = new Keybinding();

      if (binder.ShowDialog() == DialogResult.OK)
      {
        if (!(settings.HotkeyDragon == binder.Key || settings.HotkeyOurBlue == binder.Key ||
         settings.HotkeyOurRed == binder.Key || settings.HotkeyTheirBlue == binder.Key ||
         settings.HotkeyBaron == binder.Key || settings.ManualStartStopHotkey == binder.Key))
        {
          settings.HotkeyTheirRed = binder.Key;
          Settings.SaveSettings(settingsPath, settings);
          if (!started)
            InitializeLabels();
          else
            UpdateLabels(GetElapsed());
        }
        else
        {
          MessageBox.Show(this,"This is already bound!\r\nNothing has changed.", "Keybinder", MessageBoxButtons.OK);
        }
      }
    }

    private void FileSystemWatcherOnChanged(object sender, FileSystemEventArgs e)
    {
      // Nothing as of yet
    }

    private void FileSystemWatcherOnCreated(object sender, FileSystemEventArgs e)
    {
      if (started)
        return;

      if (e.FullPath.Contains("tmp"))
        return;
      currentGameFile = e.FullPath;
      Reset();
      timer.Enabled = started = true;
      autotimer.Enabled = true;
    }

    private TimeSpan GetElapsed()
    {
      if (autostart)
        return gameTime.Elapsed + TimeSpan.FromSeconds(0.75);
      return gameTime.Elapsed + settings.ManualStartTime;
    }

    private void InitializeLabels()
    {
      btnBaron.Text = string.Format("{0}\r\n\r\n00:00", settings.HotkeyBaron);
      btnDragon.Text = string.Format("{0}\r\n\r\n00:00", settings.HotkeyDragon);
      btnOurBlue.Text = string.Format("{0}\r\n\r\n00:00", settings.HotkeyOurBlue);
      btnOurRed.Text = string.Format("{0}\r\n\r\n00:00", settings.HotkeyOurRed);
      btnTheirBlue.Text = string.Format("{0}\r\n\r\n00:00", settings.HotkeyTheirBlue);
      btnTheirRed.Text = string.Format("{0}\r\n\r\n00:00", settings.HotkeyTheirRed);
   
      if (!autostart)
      {
        btnGameTime.Text = string.Format("Game Time:\r\n{0:00}:{1:00}", settings.ManualStartTime.Minutes,
                                         settings.ManualStartTime.Seconds);
      }
    }

    private void JungleTimerKeyUp(object sender, KeyEventArgs e)
    {
      ProcessHotkey(e.KeyData);
    }

    private void KOnRawInputEvent(RawInputEventArgs e)
    {
      if (e.up)
        ProcessKeyUp((Keys)e.virtualKey);
      else
        ProcessKeyDown((Keys)e.virtualKey);
    }

    private void PostMessage(string name, TimeSpan time)
    {
      if (settings.WriteToChat)
      {
        if (!string.IsNullOrWhiteSpace(settings.ChatSeparator))
        {
          Chat.PostMessage(string.Format("{0} {1} {2}:{3}", name, settings.ChatSeparator, time.ToString("mm"),
                                         time.ToString("ss")));
        }
        else
        {
          Chat.PostMessage(string.Format("{0}{1}{2}:{3}", name, settings.ChatSeparator, time.ToString("mm"),
                                         time.ToString("ss")));
        }
      }
    }

    private void ProcessHotkey(Keys key)
    {
      TimeSpan elapsed = GetElapsed();

      if (started)
      {
        if (key == settings.HotkeyBaron)
        {
          baron.Time = elapsed + Timers.Baron;
          baron.State = AnnouncementState.One;
          PostMessage(settings.ChatBaron, baron.Time);
        }
        else if (key == settings.HotkeyDragon)
        {
          dragon.Time = elapsed + Timers.Dragon;
          dragon.State = AnnouncementState.One;
          PostMessage(settings.ChatDragon, dragon.Time);
        }
        else if (key == settings.HotkeyOurBlue)
        {
          ourBlue.Time = elapsed + Timers.Blue;
          ourBlue.State = AnnouncementState.One;
          PostMessage(settings.ChatOurBlue, ourBlue.Time);
        }
        else if (key == settings.HotkeyOurRed)
        {
          ourRed.Time = elapsed + Timers.Red;
          ourRed.State = AnnouncementState.One;
          PostMessage(settings.ChatOurRed, ourRed.Time);
        }
        else if (key == settings.HotkeyTheirBlue)
        {
          theirBlue.Time = elapsed + Timers.Blue;
          theirBlue.State = AnnouncementState.One;
          PostMessage(settings.ChatTheirBlue, theirBlue.Time);
        }
        else if (key == settings.HotkeyTheirRed)
        {
          theirRed.Time = elapsed + Timers.Red;
          theirRed.State = AnnouncementState.One;
          PostMessage(settings.ChatTheirBlue, theirRed.Time);
        }
      }
      if (key == settings.ManualStartStopHotkey)
      {
        if (!started)
        {
          Reset();
          autostart = false;
          timer.Enabled = started = true;
          gameTime.Start();
        }
        else if (!autostart)
        {
          gameTime.Stop();
          timer.Enabled = started = false;
        }
      }
    }

    private void Reset()
    {
      gameTime.Stop();
      gameTime.Reset();

      baron = new Respawn();
      dragon = new Respawn();
      ourBlue = new Respawn();
      ourRed = new Respawn();
      theirRed = new Respawn();
      theirBlue = new Respawn();

      baron.Time = Timers.BaronInitial;
      dragon.Time = Timers.DragonInitial;
      ourBlue.Time = Timers.BlueInitial;
      ourRed.Time = Timers.RedInitial;
      theirBlue.Time = Timers.BlueInitial;
      theirRed.Time = Timers.RedInitial;
    }

    private void TimerOnTick(object sender, EventArgs eventArgs)
    {
      if (gameTime.IsRunning)
      {
        TimeSpan elapsed = GetElapsed();

        AnnounceRespawns(elapsed);
        UpdateLabels(elapsed);
      }
    }

    private void UpdateLabels(TimeSpan elapsed)
    {
      int min = (int)(baron.Time.TotalMinutes - elapsed.TotalMinutes);
      int sec = (int)(baron.Time.TotalSeconds - elapsed.TotalSeconds) % 60;
      Invoke(new Action(() => btnBaron.Text = string.Format("{2}\r\n\r\n{0:00}:{1:00}", min < 0 ? 0 : min, sec < 0 ? 0 : sec, settings.HotkeyBaron)));

      min = (int)(dragon.Time.TotalMinutes - elapsed.TotalMinutes);
      sec = (int)(dragon.Time.TotalSeconds - elapsed.TotalSeconds) % 60;
      Invoke(new Action(() => btnDragon.Text = string.Format("{2}\r\n\r\n{0:00}:{1:00}", min < 0 ? 0 : min, sec < 0 ? 0 : sec, settings.HotkeyDragon)));

      min = (int)(ourBlue.Time.TotalMinutes - elapsed.TotalMinutes);
      sec = (int)(ourBlue.Time.TotalSeconds - elapsed.TotalSeconds) % 60;
      Invoke(new Action(() => btnOurBlue.Text = string.Format("{2}\r\n\r\n{0:00}:{1:00}", min < 0 ? 0 : min, sec < 0 ? 0 : sec, settings.HotkeyOurBlue)));

      min = (int)(ourRed.Time.TotalMinutes - elapsed.TotalMinutes);
      sec = (int)(ourRed.Time.TotalSeconds - elapsed.TotalSeconds) % 60;
      Invoke(new Action(() => btnOurRed.Text = string.Format("{2}\r\n\r\n{0:00}:{1:00}", min < 0 ? 0 : min, sec < 0 ? 0 : sec, settings.HotkeyOurRed)));

      min = (int)(theirBlue.Time.TotalMinutes - elapsed.TotalMinutes);
      sec = (int)(theirBlue.Time.TotalSeconds - elapsed.TotalSeconds) % 60;
      Invoke(new Action(() => btnTheirBlue.Text = string.Format("{2}\r\n\r\n{0:00}:{1:00}", min < 0 ? 0 : min, sec < 0 ? 0 : sec, settings.HotkeyTheirBlue)));

      min = (int)(theirRed.Time.TotalMinutes - elapsed.TotalMinutes);
      sec = (int)(theirRed.Time.TotalSeconds - elapsed.TotalSeconds) % 60;
      Invoke(new Action(() => btnTheirRed.Text = string.Format("{2}\r\n\r\n{0:00}:{1:00}", min < 0 ? 0 : min, sec < 0 ? 0 : sec, settings.HotkeyTheirRed)));

      min = (int)(elapsed.TotalMinutes);
      sec = (int)(elapsed.TotalSeconds) % 60;
      Invoke(
        new Action(
          () => btnGameTime.Text = string.Format("Game Time:\r\n{0:00}:{1:00}", min < 0 ? 0 : min, sec < 0 ? 0 : sec)));
    }

    #endregion

    private void BtnSettingsClick(object sender, EventArgs e)
    {
      SettingsDialog settingsDialogDialog = new SettingsDialog(settings);

      if (settingsDialogDialog.ShowDialog() == DialogResult.OK)
      {
        settings = settingsDialogDialog.Settings;
        Settings.SaveSettings(settingsPath,settings);
      }
    }

    private void BtnGameTimeClick(object sender, EventArgs e)
    {
      Keybinding binder = new Keybinding();

      if (binder.ShowDialog() == DialogResult.OK)
      {
        if (!(settings.HotkeyBaron == binder.Key || settings.HotkeyOurBlue == binder.Key ||
            settings.HotkeyOurRed == binder.Key || settings.HotkeyTheirBlue == binder.Key ||
            settings.HotkeyTheirRed == binder.Key || settings.HotkeyDragon == binder.Key))
        {
          settings.HotkeyDragon = binder.Key;
          Settings.SaveSettings(settingsPath, settings);
          if (!started)
            InitializeLabels();
          else
            UpdateLabels(GetElapsed());
        }
        else
        {
          MessageBox.Show(this, "This is already bound!\r\nNothing has changed.", "Keybinder", MessageBoxButtons.OK);
        }
      }
    }
  }
}